package com.algorithms.brownian;

import java.awt.Color;

import com.algorithms.libs.stdlib.draw.StdDraw;

/**
 * Here is defined the logic for the particle and all the equations regarding
 * the interaction between the current particle and another particle. The
 * general equations for the interaction of two balls are the conservation of
 * the kinetic energy equation:<br>
 * (m1u1^2 + m2u2^2)/2 = (m1v1^2 + m2v2^2)/2, where u1, u2 are the velocities
 * before the collision and v1 and v2 are the collision after the collision and
 * the conservation of the total momentum equation:<br>
 * m1u1 + m2u2 = m1v1 + m2v2. <br>
 * Based on the previous equations we derive the new velocities and also the new
 * directions.
 * 
 * @author Andrei
 *
 */
public class Particle extends ParticleTrigonometry {

	private Double rx, ry; // position of the particle
	Double vx; // speed/velocity
	Double vy;
	private Double radius; // radius of the particle
	private Double mass; // mass of the particle
	private Color color; // color of the particle
	private int count; // number of collisions so far;

	public Particle(double rx, double ry, double vx, double vy, double radius,
			double mass, Color color) {
		this.rx = rx;
		this.ry = ry;
		this.vx = vx;
		this.vy = vy;
		this.radius = radius;
		this.mass = mass;
		this.color = color;
	}

	public Particle() {
		this.rx = Math.random();
		this.ry = Math.random();
		this.vx = 0.01 * (Math.random() - 0.5);
		this.vy = 0.01 * (Math.random() - 0.5);
		this.radius = 0.01;
		this.mass = 0.5;
		this.color = Color.black;

	}

	public void draw() {
		StdDraw.setPenColor(color);
		StdDraw.filledCircle(rx, ry, radius);
	}

	public int count() {
		return count;
	}

	public void move(double dt) {
		this.rx += this.vx * dt;
		this.ry += this.vy * dt;
	}

	public double timeToHit(Particle other) {
		Particle a = this;
		if (a == other)
			return Double.POSITIVE_INFINITY;

		double dx = other.rx - a.rx;
		double dy = other.ry - a.ry;
		double dvx = other.vx - a.vx;
		double dvy = other.vy - a.vy;
		double drdr = dx * dx + dy * dy;
		double dvdv = dvx * dvx + dvy * dvy;
		double drdv = dx * dvx + dy * dvy;
		double sigma = a.radius + other.radius;
		if (drdv > 0) {
			return Double.POSITIVE_INFINITY;
		}
		double delta = drdv * drdv - (dvdv) * (drdr - sigma * sigma);
		if (delta < 0) {
			return Double.POSITIVE_INFINITY;
		}
		return -(drdv + Math.sqrt(delta)) / dvdv;
	}

	public double timeToHitVerticalWall() {
		if (vx > 0) {
			return (double) ((1.0 - rx - radius) / vx);
		} else if (vx < 0) {
			return (radius - rx) / vx;
		} else
			return Double.POSITIVE_INFINITY;

	}

	public double timeToHitHorizontalWall() {
		if (vy > 0) {
			return (1.0 - ry - radius) / vy;
		} else if (vy < 0) {
			return (radius - ry) / vy;
		} else
			return Double.POSITIVE_INFINITY;
	}

	/**
	 * both methods bounceOffWithAngles and withoutangles basically are doing
	 * the same thing, the only difference stays in the calculation. <br>
	 * this equation is a the result of solving the following equations: <br>
	 * the kinetic energy equation:<br>
	 * (m1u1^2 + m2u2^2)/2 = (m1v1^2 + m2v2^2)/2, where u1, u2 are the
	 * velocities before the collision and v1 and v2 are the collision after the
	 * collision and the conservation of the total momentum equation:<br>
	 * m1u1 + m2u2 = m1v1 + m2v2. <br>
	 * 
	 * @param other
	 *            is the second particle with which the first particle collides
	 */

	public void bounceOffWithAngles(Particle other) {
		double m1 = this.mass;
		double m2 = other.mass;
		double dx = other.rx - this.rx;
		double dy = other.ry - this.ry;
		double phiToDegrees = getPhiAngle(dx, dy);
		double phi = 0.0;

		double omega1 = getOmegaAngle(this.vx, this.vy);
		double omega2 = getOmegaAngle(other.vx, other.vy);
		double v1 = Math.sqrt(Math.pow(this.vx, 2) + Math.pow(this.vy, 2));
		double v2 = Math.sqrt(Math.pow(other.vx, 2) + Math.pow(other.vy, 2));

		phi = Math.toRadians(phiToDegrees);
		double omega1Phi = 0.0;
		double omega2Phi = 0.0;
		double phiPlus90 = 0.0;
		double phiAngle = 0.0;

		phiAngle = phi;
		phiPlus90 = phi + Math.PI / 2;
		omega1Phi = omega1 - phi;
		omega2Phi = omega2 - phi;

		double v1xr = v1 * cos(omega1Phi);
		double v1yr = v1 * sin(omega1Phi);
		double v2xr = v2 * cos(omega2Phi);
		double v2yr = v2 * sin(omega2Phi);

		double deltaV1 = v1xr * (m1 - m2) + 2 * m2 * v2xr;

		double v1fx = deltaV1 * cos(phiAngle) / (m1 + m2) + v1yr
				* cos(phiPlus90);
		double v1fy = deltaV1 * sin(phiAngle) / (m1 + m2) + v1yr
				* sin(phiPlus90);

		// next velocity
		double deltaV2 = v2xr * (m2 - m1) + 2 * m1 * v1xr;

		double v2fx = deltaV2 * cos(phiAngle) / (m1 + m2) + v2yr
				* cos(phiPlus90);
		double v2fy = deltaV2 * sin(phiAngle) / (m1 + m2) + v2yr
				* sin(phiPlus90);

		this.vx = v1fx;
		this.vy = v1fy;
		other.vx = v2fx;
		other.vy = v2fy;
		this.count++;
		other.count++;
	}

	/**
	 * both methods bounceOffWithAngles and without angles basically are doing
	 * the same thing, the only difference stays in the calculation. <br>
	 * this equation is a derivation of the equation with angles. All it <br>
	 * remains to do is to replace the cos and sin values with their geometric
	 * representation. <br>
	 * E.g in a triangle with a 90 degree angle if we have the triangle sides a,
	 * b, c and the <br>
	 * 90 degree angle is between the b and c then: <br>
	 * cos(< b,a >) = b / a <br>
	 * and <br>
	 * sin(< b, a >) = c/a.
	 * 
	 * @param other
	 *            is the second particle with which the first particle collides
	 */
	public void bounceOffWithoutAngles(Particle other) {
		double m1 = this.mass;
		double m2 = other.mass;
		double dx = other.rx - this.rx;
		double dy = other.ry - this.ry;
		double dvx = other.vx - this.vx;
		double dvy = other.vy - this.vy;
		double distance = this.radius + other.radius;
		double dvdr = dx * dvx + dy * dvy;
		double common = 2 * m1 * m2 * dvdr / ((m1 + m2) * distance);
		double m_hvx = dx * common / distance;
		double m_hvy = dy * common / distance;
		this.vx += m_hvx / m1;
		this.vy += m_hvy / m1;
		other.vx -= m_hvx / m2;
		other.vy -= m_hvy / m2;
		this.count++;
		other.count++;
	}

	/**
	 * gets the phi angle - the collision angle
	 * 
	 * @param x1
	 *            is the x coordinate of the center of the first ball
	 * @param y1
	 *            is the y coordinate of the center of the first ball
	 * @param x2
	 *            is the x coordinate of the center of the second ball
	 * @param y2
	 *            is the y coordinate of the center of the second ball
	 * @return the angle in radians
	 */
	private double getPhiAngle(double dx, double dy) {

		if (dx == 0) {
			return Math.PI / 2;

		}
		double angle = Math.toDegrees(atan2(dy, dx));

		return angle;
	}

	/**
	 * gets the omega angle - the angle that the speed vector makes with the x
	 * axis
	 * 
	 * @param v1x
	 *            the x coordinate of the velocity of the first ball
	 * @param v1y
	 *            the y coordinate of the velocity of the first ball
	 * @return the angle
	 */
	private double getOmegaAngle(double vx, double vy) {
		double angle = 0;
		double term = 180 / Math.PI;
		if (vx == 0) {
			angle = 90;
		} else {
			return atan2(vy, vx) * term;
		}
		return angle / term;
	}

	public void bounceOffHorizontalWall() {
		vx = -vx;
		count++;
	}

	public void bounceOffVerticalWall() {
		vy = -vy;
		count++;
	}

	public double kineticEnergy() {
		return 0.5 * this.mass * (this.vx * this.vx + this.vy * this.vy);
	}
}
